package com.zndroid.utils.impl;

import android.content.Context;
import android.os.Build;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executors;

/**
 * 缓存清理大师
 * */
@SuppressWarnings("unused")
public class CleanerUtil {
    /** clean directory include '/databases','/shared_prefs','/cache','/app_webview','/code_cache','/files' of the current app*/
    public static final int DEFAULT = 0;

    /** clean directory include '/cache' of the current app*/
    public static final int ONLY_CACHE = DEFAULT + 1;

    /** clean directory include '/databases' of the current app*/
    public static final int ONLY_DB = ONLY_CACHE + 1;

    /** clean directory include '/shared_prefs' of the current app*/
    public static final int ONLY_SP = ONLY_DB + 1;

    /** clean directory include '/app_webview' of the current app*/
    public static final int ONLY_WEB_VIEW = ONLY_SP + 1;

    /** clean directory include '/databases','/cache','/app_webview','/code_cache','/files' of the current app*/
    public static final int ALL_EXCLUDE_SP = ONLY_WEB_VIEW + 1;

    /** clean directory include '/shared_prefs','/cache','/app_webview','/code_cache','/files' of the current app*/
    public static final int ALL_EXCLUDE_DB = ALL_EXCLUDE_SP + 1;

    /** clean directory include '/cache','/app_webview','/code_cache','/files' of the current app*/
    public static final int ALL_EXCLUDE_DB_SP = ALL_EXCLUDE_DB + 1;


    private File[][] table;//对应要清除的文件

    private final List<File> cleanerPaths = new ArrayList<>();
    private int cleanType = DEFAULT;
    private boolean isWithCalled = false;

    @IntDef({DEFAULT, ONLY_CACHE, ONLY_DB, ONLY_SP, ONLY_WEB_VIEW, ALL_EXCLUDE_SP, ALL_EXCLUDE_DB, ALL_EXCLUDE_DB_SP})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Type {}

    public CleanerUtil with(Context context) {
        isWithCalled = true;

        //常使用的缓存路径
        File path_databases;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            path_databases = new File(context.getDataDir(), "databases");
        } else {
            path_databases = new File(context.getFilesDir().getParentFile(), "databases");
        }
        File path_files = context.getFilesDir();
        File path_shared_prefs = new File(context.getFilesDir().getParentFile(), "shared_prefs");
        File path_cache = context.getCacheDir();
        File path_app_web_view = new File(context.getFilesDir().getParentFile(), "app_webview");
        File path_code_cache;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            path_code_cache = context.getCodeCacheDir();
        } else {
            path_code_cache = new File(context.getFilesDir(), "code_cache");
        }

        // m*n  切记和Type对应
        table = new File[][]{
                //DEFAULT
                {path_cache, path_files, path_databases, path_shared_prefs, path_app_web_view, path_code_cache},
                //ONLY_CACHE
                {path_cache},
                //ONLY_DB
                {path_databases},
                //ONLY_SP
                {path_shared_prefs},
                //ONLY_WEB_VIEW
                {path_app_web_view},
                //ALL_EXCLUDE_SP
                {path_cache, path_files, path_databases, path_app_web_view, path_code_cache},
                //ALL_EXCLUDE_DB
                {path_cache, path_files, path_shared_prefs, path_app_web_view, path_code_cache},
                //ALL_EXCLUDE_DB_SP
                {path_cache, path_files, path_app_web_view, path_code_cache}
        };
        return this;
    }

    /**
     * set clean type
     * */
    public CleanerUtil setType(@Type int cleanType) {
        check();

        this.cleanType = cleanType;
        return this;
    }

    /**
     * add clean path for custom
     * */
    public CleanerUtil addCleanPath(@NonNull String ... paths) {
        check();

        for (String s : paths) {
            cleanerPaths.add(new File(s));
        }
        return this;
    }

    /**
     * get clean size for type,if you use custom path call {@link #addCleanPath(String...)} before call this
     * */
    public long getSize() {
        check();

        long size = 0;
        trimPath();

        FileUtil fileUtil = new FileUtil();
        for (File file : cleanerPaths) {
            if (file.exists()) {
                size += fileUtil.getSize(file);
            }
        }
        return size;
    }

    /**
     * format size for user inner impl, like 0.00 'B\KB\MB\TB',if you use custom path call {@link #addCleanPath(String...)} before call this
     * */
    public String getFormatSize() {
        check();

        long size = 0;
        trimPath();

        FileUtil fileUtil = new FileUtil();
        for (File file : cleanerPaths) {
            if (file.exists()) {
                size += fileUtil.getSize(file);
            }
        }

        return fileUtil.formatSize(size);
    }

    /**
     * clean and use cleanType,if you use custom path call {@link #addCleanPath(String...)} before call this
     * */
    public void clean() {
        check();
        trimPath();

        FileUtil fileUtil = new FileUtil();
        for (File file : cleanerPaths) {
            fileUtil.deleteAll(file);
        }
    }

    /**
     * clean and use cleanType async,if you use custom path call {@link #addCleanPath(String...)} before call this
     * */
    public void asyncClean(@NonNull final ICleanCallBack callBack) {
        check();
        trimPath();

        final long length = getSize();
        hasDeleteSize = 0;

        Executors.newSingleThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                for (File file : cleanerPaths) {
                    deleteAll(length, file, callBack);
                }
            }
        });

    }

    private void trimPath() {
        //使用前先清空上次保存的路径集合
        cleanerPaths.clear();
        //type <--> table
        cleanerPaths.addAll(Arrays.asList(table[cleanType]));
    }

    private void check() {
        if (!isWithCalled) {
            throw new UnsupportedOperationException("pls call #with(your context[not application]) at first");
        }

    }

    public interface ICleanCallBack {
        /**
         * current delete progress, max is 100
         * @param progress progress
         * */
        void onCleaning(int progress);
        /**
         * clean ok
         * */
        void onCleanOK();
    }

    private long hasDeleteSize = 0;
    private void deleteAll(long size, @NonNull File file, ICleanCallBack callBack) {
        try{
            if(!file.exists()){
                return;
            }

            if(file.isFile()) {
                hasDeleteSize += file.length();
                file.delete();
                callBack.onCleaning((int) (hasDeleteSize * 100 / size));
                if (hasDeleteSize == size) {
                    callBack.onCleanOK();
                }
            } else if (file.isDirectory()){
                File[] files = file.listFiles();
                assert files != null;
                for (File value : files) {
                    //得到子文件或文件夹的绝对路径
                    String root = value.getAbsolutePath();
                    deleteAll(size, new File(root), callBack);
                }
                file.delete();
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 清除所有缓存，相当于设置里应用管理中的“清除数据”，清除数据时应用将会退出
     * @param context context
     */
    public void clearAppUserData(Context context) {
        try {
            Runtime.getRuntime().exec("pm clear " + context.getPackageName());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
